﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;

/**
 * \file ViewableObject.cs
 */

/**
 * This is an object with an associated Animation.
 * ViewableObject instances can
 * be seen in the game environment and have
 * a collision box that should interfere with
 * the player's movement.
 */
public class ViewableObject : CollidableObject
{
    /**used to store the currently active Animation
     * so that we always reference the right one in 
     * update and draw
     */
    protected Animation currentAnimation;
    /**
     * the only animation for ViewableObjects
     * (usually one sprite).
     */
    protected Animation stasis;

    public float spriteDepth
    {
        get { return sDepth; }
    }
    protected float sDepth;

    

    /**
     * read-only, if this object is allowed
     * to be walked on.
     * @see ViewableObject.walkable
     */
    public Boolean walkable
    {
        get { return thisType.walkable; }
    }


    /**specifies exactly what type of ViewableObject
     * this instance is.
     */
    protected new ViewableObject.Type thisType;

    /**
     * the x,y coordinates in relation
     * to the camera so I can compute that in
     * update() and use it in draw()
     */
    protected Vector3 screenCoord;

    /**
     * creates a ViewableObject using the specified information.
     * using this will cause this object's SpawnPointer to be null
     * @param type the specific type that this should be
     * @param placement the world x,y,z coordinates for this object
     * @see ObjectFactory.TypeEnum
     */
    public ViewableObject(ViewableObject.Type type, Vector3 placement)
        : base(type, placement)
    {
        init(type, placement);
    }

    private void init(ViewableObject.Type type, Vector3 placement)
    {
        thisType = type;
        stasis = new Animation(thisType.idleName, thisType.idleFramerate, thisType.idleLoopable, thisType.idleFrames);
        currentAnimation = stasis;
        sDepth = thisType.spriteDepth;
    }

    /**
     * An update that doesn't update logics. Used by the LevelEditor to move
     * the position of an object after creation.
     */
    public void editorUpdate()
    {
        screenCoord = Camera.worldToScreen(origin);
    }

    /**
     * Updates the draw coordinates for this object.
     * uses CollidableObject's update to do other
     * processing.
     * @param elapsed the number of milliseconds
     * since the last update
     * @see CollidableObject.update()
     */
    public override void update(double elapsed)
    {
        base.update(elapsed);
        if (!cull)
        {
            currentAnimation.update(elapsed);
            screenCoord = Camera.worldToScreen(origin);
        }
    }

    /**
     * draws this instance to the screen if it is viewable.
     * uses Camera.worldToScreen() to get the appropriate x,y
     * coordinates for drawing
     * @param elapsedTime the number of milliseconds that
     * have passed since the last draw command was called
     * @see Camera.worldToScreen()
     */
    public override void draw()
    {
        base.draw();
        currentAnimation.render(new Vector2(screenCoord.X, screenCoord.Y), screenCoord.Z+(thisType.depthOffset*thisType.spriteDepth));
    }

    public new class Type : CollidableObject.Type
    {
        //static world objects
        public static readonly Type Bench = new Type(
            ObjectFactory.TypeEnum.Bench, 
            new Vector3(74,96,0), 
            new Vector3(152,96,50),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("bench"),
                    new Rectangle(0,0,162,96), 
                    0, 
                    new Vector2(81,92))});

        public static readonly Type Mailbox = new Type(
            ObjectFactory.TypeEnum.Mailbox,
            new Vector3(27, 95, 0),
            new Vector3(54, 95, 60),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("mailbox"),
                    new Rectangle(0,0,66,95), 
                    0, 
                    new Vector2(33,90))});

        public static readonly Type StreetLight = new Type(
            ObjectFactory.TypeEnum.StreetLight,
            new Vector3(15, 378, 0),
            new Vector3(30, 378, 30),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("streetLight"),
                    new Rectangle(0,0,30,378), 
                    0, 
                    new Vector2(15,378))});

        public static readonly Type TrashCan = new Type(
            ObjectFactory.TypeEnum.TrashCan,
            new Vector3(35, 125, 0),
            new Vector3(70, 125, 70),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("trashCan"),
                    new Rectangle(0,0,84,125), 
                    0, 
                    new Vector2(42,120))});

        public static readonly Type OozeContainer = new Type(
            ObjectFactory.TypeEnum.OozeContainer,
            new Vector3(55, 72, 0),
            new Vector3(100, 72, 70),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("oozeContainer"),
                    new Rectangle(0,0,110,72), 
                    0, 
                    new Vector2(55,65))});

        public static readonly Type LargeDebris2 = new Type(
            ObjectFactory.TypeEnum.LargeDebris2,
            new Vector3(111, 240, 0),
            new Vector3(222, 240, 130),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("largeDebris2"),
                    new Rectangle(0,0,222,258), 
                    0, 
                    new Vector2(111,258))});

        public static readonly Type LargeDebris3 = new Type(
            ObjectFactory.TypeEnum.LargeDebris3,
            new Vector3(108, 110, 0),
            new Vector3(217, 110, 110),
            true,//walkable
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("largeDebris3"),
                    new Rectangle(0,0,217,165), 
                    0, 
                    new Vector2(108,165))},//frameList
            "LDebris3Stasis",//animation name
            0,//framerate
            true);//loopable

        public static readonly Type LargeDebris4 = new Type(
            ObjectFactory.TypeEnum.LargeDebris4,
            new Vector3(111, 178, 0),
            new Vector3(223, 178, 200),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("largeDebris4"),
                    new Rectangle(0,0,223,178), 
                    0, 
                    new Vector2(111,178))});

        public static readonly Type Debris2 = new Type(
            ObjectFactory.TypeEnum.Debris2,
            new Vector3(34, 64, 0),
            new Vector3(68, 64, 60),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("Debris2"),
                    new Rectangle(0,0,68,64), 
                    0, 
                    new Vector2(34,64))});

        public static readonly Type Debris3 = new Type(
            ObjectFactory.TypeEnum.Debris3,
            new Vector3(25, 50, 0),
            new Vector3(50, 50, 50),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("Debris3"),
                    new Rectangle(0,0,50,50), 
                    0, 
                    new Vector2(25,50))});

        public static readonly Type Debris4 = new Type(
            ObjectFactory.TypeEnum.Debris4,
            new Vector3(34, 55, 0),
            new Vector3(68, 55, 60),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("Debris4"),
                    new Rectangle(0,0,68,55), 
                    0, 
                    new Vector2(34,55))});

        public static readonly Type Debris5 = new Type(
            ObjectFactory.TypeEnum.Debris5,
            new Vector3(18, 30, 0),
            new Vector3(36, 30, 30),
            new ArrayList{ 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("Debris5"),
                    new Rectangle(0,0,36,30), 
                    0, 
                    new Vector2(18,30))});
        
        public static readonly Type BrickWall = new Type();
        public static readonly Type Window = new Type();
        public static readonly Type BrokenDoor = new Type();
        public static readonly Type Debris = new Type(
            ObjectFactory.TypeEnum.Debris,//type
            new Vector3(102,156,0),//origin
            new Vector3(205,156,205),//collision
            new ArrayList {
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.DEBRIS).get("debris"), 
                    new Rectangle(0,0,205,156), 
                    0, 
                    new Vector2(102,156))});//frameList
        public static readonly Type DebrisPile = new Type();
        public static readonly Type PowerPole = new Type(
            ObjectFactory.TypeEnum.PowerPole,//type
            new Vector3(12, 375, 0),//origin
            new Vector3(24, 375, 24),//collision
            new ArrayList {
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("electricPole"), 
                    new Rectangle(0,0,49,425), 
                    0, 
                    new Vector2(24,425))});//frameList

        public static readonly Type PowerPoleNWires = new Type(//put these 638 apart
            ObjectFactory.TypeEnum.PowerPoleNWires,//type
            new Vector3(12, 375, 0),//origin
            new Vector3(24, 375, 24),//collision
            new ArrayList {
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("electricPoleNWires"), 
                    new Rectangle(0,0,662,424), 
                    0, 
                    new Vector2(24,425))});//frameList

        public static readonly Type BrokenPowerPole = new Type(//put these 638 apart
            ObjectFactory.TypeEnum.BrokenPowerPole,//type
            new Vector3(41, 223, 0),//origin
            new Vector3(105, 223, 70),//collision
            new ArrayList {
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("powerPoleDowned"), 
                    new Rectangle(0,0,142,223), 
                    0, 
                    new Vector2(71,223))});//frameList

        public static readonly Type SewerWall = new Type();
        public static readonly Type SewerFloor = new Type();
        public static readonly Type Street = new Type(
            ObjectFactory.TypeEnum.Street,//type
            new Vector3(512,0,0),//origin
            new Vector3(1024,0,1999),//collision
            true,//walkable
            new ArrayList { 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STREET).get("street"), 
                    new Rectangle(0, 0, 1024, 863), 
                    0, 
                    new Vector2(512, 863)) },//frameList
            "StreetStasis",//animation name
            0,//framerate
            true);//loopable

        public static readonly Type Building1 = new Type(
            ObjectFactory.TypeEnum.Building1,//type
            new Vector3(224, 500, 0),//origin
            new Vector3(449, 500, 999),//collision
            true,//walkable
            new ArrayList { 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("building1"), 
                    new Rectangle(0, 0, 449, 711), 
                    0, 
                    new Vector2(224, 711)) },//frameList
            "building1Stasis",//animation name
            0,//framerate
            true);//loopable

        public static readonly Type Building2 = new Type(
            ObjectFactory.TypeEnum.Building2,//type
            new Vector3(224, 500, 0),//origin
            new Vector3(448, 500, 999),//collision
            true,//walkable
            new ArrayList { 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.STATIC_WORLD_OBJECTS).get("building2"), 
                    new Rectangle(0, 0, 448, 711), 
                    0, 
                    new Vector2(224, 711)) },//frameList
            "building2Stasis",//animation name
            0,//framerate
            true);//loopable
        public static readonly Type Hero = new Type(
            ObjectFactory.TypeEnum.Hero,
            new Vector3(60, 179, 0),
            new Vector3(121,179,121),
            new ArrayList { 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.HERO).get("heroIdleTest"), 
                    new Rectangle(0, 0, 121, 179), 
                    0, 
                    new Vector2(60, 179)) });
        public static readonly Type DeadHero = new Type(
            ObjectFactory.TypeEnum.DeadHero,
            new Vector3(82, 0, 0),
            new Vector3(165, 0, 110),
            new ArrayList { 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.HERO).get("heroDead"),
                    new Rectangle(0, 0, 165, 55),
                    0,
                    new Vector2(82, 55)) });
        public static readonly Type FlatHero = new Type(
            ObjectFactory.TypeEnum.FlatHero,
            new Vector3(92, 0, 0),
            new Vector3(185, 0, 86),
            new ArrayList { 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.HERO).get("heroDeadFlat"),
                    new Rectangle(0, 0, 185, 43),
                    0,
                    new Vector2(92, 43)) });
        public static readonly Type BurnedHero = new Type(
            ObjectFactory.TypeEnum.BurnedHero,
            new Vector3(76, 0, 0),
            new Vector3(152, 0, 100),
            new ArrayList { 
                new Frame(
                    Game.getGame().getAssetLibrary().get(AssetLibrary.assetType.HERO).get("heroDeadBurned"),
                    new Rectangle(0, 0, 152, 54),
                    0,
                    new Vector2(76, 54)) });


        //animation info for the stasis animation
        //but each instance of ViewableObject 
        //needs its own Animation instance
        /** the frame information for the stasis animation */
        public ArrayList idleFrames
        {
            get { return stasisFrames; }
        }
        protected ArrayList stasisFrames;
        /** the name for the stasis animation */
        public String idleName
        {
            get { return stasisName; }
        }
        protected String stasisName;
        /** the framerate for the stasis animation */
        public int idleFramerate
        {
            get { return stasisFramerate; }
        }
        protected int stasisFramerate;
        /** whether or not the stasis animation is loopable */
        public Boolean idleLoopable
        {
            get { return stasisLoopable; }
        }
        protected Boolean stasisLoopable;
        public float depthOffset
        {
            get { return depthOffsetAmount; }
        }
        protected float depthOffsetAmount;
        /**
         * read-only, if this object is allowed
         * to be walked on.
         */
        public Boolean walkable
        {
            get { return walkOn; }
        }
        protected Boolean walkOn = false;

        /**
         * read-only float, the default placement of the sprite 
         * depth-wise in its collision cube. 1 means the 
         * back of the cube, 0 means the front.
         */
        public float spriteDepth
        {
            get { return sDepth; }
        }
        protected float sDepth = 1;

        /**
         * this is a dummy constructor.
         */
        protected Type() { }

        /**
         * Private constructor.
         * This private constructor ensures that only 
         * the pre-specified Type objects can be used 
         * and compared against.
         * @param type the type of this object.
         * @param origin the point that is used for placement of this
         * object's collision cube.
         * @param collision the size of the collision cube.
         * @param walkable whether or not this object can be walked on.
         * @param stasisFrames the ArrayList of frames for this objects
         * stasis animation.
         * @param stasisName the name for the stasis animation.
         * @param stasisFramerate framerate for the stasis animation.
         * @param stasisLoopable whether the stasis animation should loop.
         */
        protected Type(ObjectFactory.TypeEnum type,Vector3 origin, Vector3 collision, Boolean walkable, 
            ArrayList stasisFrames, String stasisName, int stasisFramerate, Boolean stasisLoopable)
            : base(type, origin, collision)
        {
            init(collision, walkable, stasisFrames, stasisName, stasisFramerate, stasisLoopable);
        }

        protected Type(ObjectFactory.TypeEnum type, Vector3 origin, Vector3 collision,
            ArrayList stasisFrames)
            : base(type, origin, collision)
        {
            init(collision, false, stasisFrames, "Stasis", 12, true);
        }

        protected void init(Vector3 collision, Boolean walkable, ArrayList stasisFrames, String stasisName, int stasisFramerate, Boolean stasisLoopable)
        {
            float depthOffsetOffset = Camera.worldToScreen(new Vector3(0, 0, 0)).Z;
            depthOffsetAmount = Camera.worldToScreen(collision).Z-depthOffsetOffset;
            walkOn = walkable;
            this.stasisFrames = stasisFrames;
            this.stasisName = stasisName;
            this.stasisFramerate = stasisFramerate;
            this.stasisLoopable = stasisLoopable;
            foreach (Frame f in stasisFrames)
            {
                if (f.origin.X > -cullMinOffset.X)
                {
                    cullMinOffset.X = -f.origin.X;
                }
                if (f.origin.Y > -cullMinOffset.Y)
                {
                    cullMinOffset.Y = -f.origin.Y;
                }
                if (f.sourceRectangle.Width - f.origin.X > cullMaxOffset.X)
                {
                    cullMaxOffset.X = f.sourceRectangle.Width - f.origin.X;
                }
                if (f.sourceRectangle.Height - f.origin.Y > cullMaxOffset.Y)
                {
                    cullMaxOffset.Y = f.sourceRectangle.Height - f.origin.Y;
                }
            }
            //and make sure no frame has bigger max/min cull boxes than the collision box cull boxes
        }

        public static ViewableObject.Type getType(ObjectFactory.TypeEnum te)
        {
            switch (te)
            {
                case ObjectFactory.TypeEnum.BrickWall: return BrickWall;
                case ObjectFactory.TypeEnum.Window: return Window;
                case ObjectFactory.TypeEnum.BrokenDoor: return BrokenDoor;
                case ObjectFactory.TypeEnum.Debris: return Debris;
                case ObjectFactory.TypeEnum.DebrisPile: return DebrisPile;
                case ObjectFactory.TypeEnum.PowerPole: return PowerPole;
                case ObjectFactory.TypeEnum.SewerWall: return SewerWall;
                case ObjectFactory.TypeEnum.SewerFloor: return SewerFloor;
                case ObjectFactory.TypeEnum.Street: return Street;
                case ObjectFactory.TypeEnum.Hero: return Hero;
                case ObjectFactory.TypeEnum.FlatHero: return FlatHero;
                case ObjectFactory.TypeEnum.DeadHero: return DeadHero;
                case ObjectFactory.TypeEnum.Bench: return Bench;
                case ObjectFactory.TypeEnum.LargeDebris2: return LargeDebris2;
                case ObjectFactory.TypeEnum.LargeDebris3: return LargeDebris3;
                case ObjectFactory.TypeEnum.LargeDebris4: return LargeDebris4;
                case ObjectFactory.TypeEnum.Debris2: return Debris2;
                case ObjectFactory.TypeEnum.Debris3: return Debris3;
                case ObjectFactory.TypeEnum.Debris4: return Debris4;
                case ObjectFactory.TypeEnum.Debris5: return Debris5;
                case ObjectFactory.TypeEnum.Building1: return Building1;
                case ObjectFactory.TypeEnum.Building2: return Building2;
                case ObjectFactory.TypeEnum.PowerPoleNWires: return PowerPoleNWires;
                case ObjectFactory.TypeEnum.BurnedHero: return BurnedHero;
                case ObjectFactory.TypeEnum.Mailbox: return Mailbox;
                case ObjectFactory.TypeEnum.StreetLight: return StreetLight;
                case ObjectFactory.TypeEnum.TrashCan: return TrashCan;
                case ObjectFactory.TypeEnum.OozeContainer: return OozeContainer;
                case ObjectFactory.TypeEnum.BrokenPowerPole: return BrokenPowerPole;
            }
            return null;
        }
    }
}
